'''
Created on 18.11.2012

@author: B. Wilbertz
'''

import threading, logging
from PyCoiGlobals import pumpInterval,maxTimeDelta,tempSensorID,rainModeFactor,nonlinearSpeedProgression,crossModeFactor,NPumpStrokesRegular,RainModeAddPumpStrokes,CrossModeAddPumpStrokes,XtraModeDryDistance,XtraModeMaxPumpStrokes,XtraModeRainDistancePerStroke
from Utilities import xstr
from DS18B20 import DS18B20

class OperatingState:
    '''
    Represents the core state of the RaspberryCoi
    and provides important control functionalities
    like updating the nPumpStrokes and the pumpInterval
    as well as the GPS state. 
    '''
    
    class GPSstate:
        gpsTime = None
        gpsAlt = None
        gpsSpeed = None
        gpsSpeedMS = None
        gpsFix = 0
        gpsTag = None
        gpsSat = 0
        
        gpsDistance = 0
        lastDistUpdateTime = None
        
        def updateFromTPV(self, report):
            from Utilities import msec2kmh
            self.gpsTime = report.get("time")
            self.gpsAlt = report.get("alt")
            self.gpsSpeedMS = report.get("speed")
            self.gpsSpeed = msec2kmh(self.gpsSpeedMS)
            self.gpsFix = report.get("mode")
            self.gpsTag = report.get("tag")
            
            
        def updateFromSKY(self, report):
            count = 0
            for sat in report.satellites:
                if sat.used:
                    count += 1
                    
            self.gpsSat = count
                    
                    
        def __str__(self):
            return 'gpsState [gpsFix=' + xstr(self.gpsFix) \
                + ', gpsTime=' + xstr(self.gpsTime) \
                + ', gpsSat=' + xstr(self.gpsSat) \
                + ', gpsAlt=' + xstr(self.gpsAlt) \
                + ', gpsSpeed=' + xstr(self.gpsSpeed) \
                + ', gpsSpeedMS=' + xstr(self.gpsSpeedMS) \
                + ', gpsTag=' + xstr(self.gpsTag) \
                + ', gpsDistance=' + xstr(self.gpsDistance) \
                + ', lastDistUpdateTime=' + xstr(self.lastDistUpdateTime) \
                + ']'
    
    OK = True
    
    rainMode = False            # set by GPIO thread through setRainMode
    rainModeTearDown = False    # set by GPIO thread through setRainMode and eventually turned of by updatePumpState
    
    crossMode = False
    crossModeNow = False
    crossModeEvent = threading.Event()
    
    pumpEvent = threading.Event()
    nPumpStrokes = 0
    
    gpsState = GPSstate()
    
    displayConnection = None
        
    reedDistance = 0
    
    distance = 0
    oilDistance = 0
    lastPump = 0
    
    rainModeOn = 0              # set by GPIO thread through setRainMode
    rainModeOff = 0             # set by GPIO thread through setRainMode
    
    xtraModePumpStrokes = 0     # set by updatePumpState
    xtraModeLastIncrement = 0   # set by updatePumpState
    
    tempSensor = DS18B20(tempSensorID)
    temperature = -99.0
    
    
    def setRainMode(self, isRaining):
        if (isRaining):
            if not self.rainMode:
                self.rainModeOn = self.distance
                self.rainModeTearDown = False
                self.xtraModeLastIncrement = self.distance
                
            self.rainMode = True
        else:
            if self.rainMode:
                self.rainModeOff = self.distance
                self.rainModeTearDown = True
                
            self.rainMode = False
    
    def setCrossMode(self, isCross):
        if (isCross):
            if not self.crossMode:
                self.crossModeNow = True
                self.crossMode = True
                logging.info("Cross mode on " + xstr(self))
                self.crossModeEvent.set()
                
            self.crossMode = True
        else:
            if self.crossMode:
                self.crossMode = False
                logging.info("Cross mode off " + xstr(self))
                self.crossModeEvent.set()
                
            self.crossMode = False
            
    def updateTemperature(self):
        if self.tempSensor.OK:
            self.temperature = self.tempSensor.getTemp()
    
    def updateDistance(self):
        '''
        updates
         - lastDistUpdateTime
         - gpsDistance 
         - distance
         - oilDistance
        from gpsState
        '''
        from Utilities import isotime_diff, isSpeedValid, nonLinearFactor
        state = self.gpsState 
        if state.gpsFix >= 2:
            timeDelta = isotime_diff(state.lastDistUpdateTime, state.gpsTime)
            if timeDelta >= 0 and timeDelta < maxTimeDelta:
                if isSpeedValid(state.gpsSpeedMS):
                    delta = state.gpsSpeedMS * timeDelta
                    state.gpsDistance += delta
                    self.distance += delta
                    
                    if self.rainMode:
                        delta = delta / rainModeFactor
                        
                    if self.crossMode:
                        delta = delta / crossModeFactor
                        
                    # we wanna do non-linear speed progression only if we are not in RainMode and not in CrossMode
                    if (nonlinearSpeedProgression and not self.rainMode and not self.crossMode):
                        self.oilDistance += delta * nonLinearFactor(state.gpsSpeed)
                    else:
                        self.oilDistance += delta
                        
                elif state.gpsSpeed != 0.0:
                    logging.debug('Invalid speed, not updating distance: ' + xstr(state))
            else:
                logging.warning('Invalid timeDelta ' + xstr(timeDelta) + ' for ' + xstr(state))
                
            state.lastDistUpdateTime = state.gpsTime
        else:
            logging.info('Not updating distance in this cycle due to bad GPS fix: ' + xstr(state))
        
    def updatePumpState(self):
        '''
        determines how much to pump and updates nPumpStrokes or sets it to 0 if nothing has to be done
        '''
        # default is no pumping
        self.nPumpStrokes = 0
        
        # setting nPumpStrokes
        nStrokes = NPumpStrokesRegular
        
        if ( self.rainMode ):
            nStrokes += RainModeAddPumpStrokes
        
        if ( self.crossMode ):
            nStrokes += CrossModeAddPumpStrokes
            # here we already enable cross mode now pumping 
            if ( self.crossModeNow ):
                logging.info("ControlThread: Trigger pumping in cross mode now")
                self.nPumpStrokes = nStrokes
                self.crossModeNow = False
                self.lastPump = self.oilDistance
        
        # this is the usual trigger for pumping
        distanceSinceLastPump = self.oilDistance - self.lastPump
        
        if ( distanceSinceLastPump >= pumpInterval ):
            logging.info("ControlThread: Trigger pumping due to interval limit reached.")
            self.nPumpStrokes = nStrokes
            self.lastPump += pumpInterval
            
        # handle XTraMode 
        if ( self.rainMode ):
            distanceSinceLastIncrement = self.distance - self.xtraModeLastIncrement
            if ( distanceSinceLastIncrement >= XtraModeRainDistancePerStroke ):
                self.xtraModePumpStrokes += 1
                self.xtraModeLastIncrement = self.distance
                
                logging.info("Increased xtraModePumpStrokes by 1: " + str(self))
        elif( self.rainModeTearDown ):
            distanceSinceRainStop = self.distance - self.rainModeOff
            if ( distanceSinceRainStop >= XtraModeDryDistance ):
                logging.info("ControlThread: Trigger pumping after rain period.")
                self.nPumpStrokes += min(self.xtraModePumpStrokes, XtraModeMaxPumpStrokes)
                logging.info("XtraMode after rain: " + str(self))
                
                # resetting xtra mode stuff
                self.rainModeTearDown = False
                self.xtraModePumpStrokes = 0
                
                if ( self.nPumpStrokes > 0 ):
                    self.lastPump = self.oilDistance
        
        
    def setInitialGPSTime(self, time):
        self.gpsState.gpsTime = time
        self.gpsState.lastDistUpdateTime = time
        
    def __str__(self):
        return 'State [ok=' + xstr(self.OK)\
            + ', distance=' + xstr(self.distance) \
            + ', oilDistance=' + xstr(self.oilDistance) \
            + ', lastPump=' + xstr(self.lastPump) \
            + ', nPumpStrokes=' + xstr(self.nPumpStrokes) \
            + ', gpsState=' + xstr(self.gpsState) \
            + ', temp=' + xstr(self.temperature) \
            + ', crossMode=' + xstr(self.crossMode) \
            + ', rainMode=' + xstr(self.rainMode) \
            + ', rainModeTearDown=' + xstr(self.rainModeTearDown) \
            + ', xtraModePumpStrokes=' + xstr(self.xtraModePumpStrokes) \
            + ', rainModeOn=' + xstr(self.rainModeOn) \
            + ', rainModeOff=' + xstr(self.rainModeOff) \
            + ']'